Skip to content

refactor(runtime): remove obsolete permission mode compatibility path - #5300

Open
chinawch007 wants to merge 3 commits into
apache:mainfrom
chinawch007:refactor/remove-permission-mode-compat-4795
Open

chinawch007 wants to merge 3 commits into
apache:mainfrom
chinawch007:refactor/remove-permission-mode-compat-4795

Conversation

@chinawch007

@chinawch007 chinawch007 commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #4795

Remove the unused SessionManager.setPermissionMode wrapper, its legacy-store fallback, and the unused SessionManager.setExecutionBoundaryKind entry point. Desktop and CLI continue changing permissions through session.configuration.updatetransitionSessionConfiguration, leaving one Runtime authority for persisted permission changes.

  • Remove Runtime's unversioned boundary-write requirement, obsolete helpers, and test stubs. Move widening/narrowing, descendant shell revocation, Deep Research cleanup, and pending Interaction coverage onto configuration authority. Assert stale configuration revisions and concurrent boundary conflicts separately with deterministic tests.
  • Preserve executorId in migrated test configurations and verify permission widening with an active plugin-executor Turn. Restore the test Store's guard against ordinary or versioned header writes that bypass configuration authority.
  • Keep Runtime's paired configuration capabilities optional for stores that do not mutate configuration; production SessionAuthorityStore requires both. Document the atomic revision/configuration/boundary contract and test each missing capability.

Storage's public boundary mutation API is deliberately outside this Runtime cleanup. Its private setExecutionBoundaryKindSync remains in use by the versioned configuration transaction.

Production permission switching and same-configuration no-op behavior are unchanged. Historical mode_change writes were removed in #4879; current boundary logging remains unchanged.

Verification

Passed locally at 239da0784 after rebuilding:

  • npm run lint, npm run format:check, npm run build, and npm run typecheck.
  • knip --workspace apps/desktop and knip --workspace packages/ui, using the installed repository binary.
  • 560 tests, 0 failures across nine compiled test files: Runtime session-manager, runtime-kernel-interaction, session-manager-terminal-ledger, and runtime-event-read-model; Host session-catalog-coordinator and execution-model-composition; Storage sqlite-session-metadata-store; CLI runtime-host-session-driver; Desktop runtime-host-client-uds. These include Host permission regressions for ordinary Turns and active Goal continuations.
  • Removing the executorId copy makes the new regression test fail with session_busy. Injecting ordinary or versioned header permission writes fails at the restored Store guard; disabling that guard makes the ordinary bypass pass again. All temporary mutations were restored before the final regression run.

Not run: the full workspace test suite or manual Desktop interaction. Existing behavioral tests preserve the API-removal semantics; the failing-before check specifically covers the executor-preservation correction.

AI use

Select exactly one:

  • No generative tool made a substantive contribution
  • Generative tooling made a substantive contribution

Tool(s) and scope: Codex implemented the cleanup and review follow-ups, migrated and extended tests, ran verification, and drafted this description. Affected commits include Generated-by: Codex; retain the trailer in the final squash commit.

Checklist

  • Tests cover the change and fail without it
  • Lint, format, typecheck and the affected suites pass locally

Does this PR entail a change in behavior?

  • Yes — described under Summary above
  • No

@github-actions github-actions Bot added the effort/M Under 500 readable lines label Sep 14, 2026
Remove SessionManager.setPermissionMode, its legacy-store fallback, and
helpers used only by that path. Production Desktop and CLI permission
changes continue through the versioned configuration authority.

Migrate concurrency, Deep Research cleanup, and pending Interaction tests
to transitionSessionConfiguration. Document the paired optional Store
capabilities and verify missing capabilities reject without fallback writes.

Fixes apache#4795

Generated-by: Codex
@chinawch007
chinawch007 force-pushed the refactor/remove-permission-mode-compat-4795 branch from 5d5e674 to 1d845e4 Compare September 14, 2026 12:36

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for picking this up. Reviewed at head 1d845e4. I re-verified the premise on current main: SessionManager.setPermissionMode has no production caller (Desktop sessions:setPermissionMode and the CLI driver both go through session.configuration.updatetransitionSessionConfiguration), and the removed legacy fallback was not a faithful mirror of the authority (no revision fence, no archived check, non-atomic header + boundary writes). So the removal is a real entropy reduction, and transitionSessionConfiguration covers every check the deleted helpers had. Both production paths are unaffected. Lint, format and the compiled session-manager suite (191/191) pass on this head; the migrated tests also pass on main, which matches what the description says.

Two things I'd like to see before this lands, both about the one authority acceptance criterion in #4795:

  1. setExecutionBoundaryKind is the same shape as the path this PR deletes. packages/runtime/src/session-manager.ts:1653-1688 derives a permissionMode itself and persists it through store.setExecutionBoundaryKind(...), which in sqlite-session-metadata-store.ts:4889-4899 patches permissionMode and labels into the header with no version fence, and without the deep-research cleanup, archived check or revision check that transitionSessionConfiguration applies. It also has zero production callers (the only non-test caller, run-command-core.ts:343, resolves to RuntimeHostRunRuntime, which forwards to the driver's configuration update). The test file now promotes it to a 'direct' route, which gives it a longer life. By the issue's own standard I think it should go in the same change, or the PR should say why it can't (I couldn't find a blocker: same file, same policy, same caller count).

  2. The description says capabilities stay optional, but not why. The reason is checkable and worth writing down: SessionAuthorityStore already requires readHeaderRecordSnapshot and updateSessionConfiguration, the only production store implements both, and optionality only serves a handful of test fixtures. Making them required would also touch relocateSessionWorkspace, which shares requireSessionConfigurationStore, so keeping them optional here is the right scope. Saying that answers the review item in #4795 instead of stepping around it.

Line-level notes are inline. One small correction for the description: the migrated tests pass on main unchanged, so the "fail without it" box should stay unticked, which is fine for a pure removal.

AI assistance: I used Claude Code to survey callers and run the ablation; every finding above was checked by me against the code and the test run.

// Either the configuration revision or the boundary revision can fence
// the stale request, depending on when its snapshot was observed.
if (
route === 'configuration' &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2. With each request reading its own snapshot inside update(), the two requests are no longer guaranteed to observe the same revision, so the assertion accepts either SessionConfigurationRevisionConflictError or operation_conflict. Those map to different client-visible outcomes at the Host (configurationSuccess(revisionConflict) vs configurationFailure), so the test no longer pins which one a stale request gets. The old 'legacy' route was deterministic. Suggest reading both snapshots before Promise.allSettled so both requests share one expectedRevision, then asserting the revision-conflict branch only. (The comment above about both requests observing Explore also no longer matches the code.)

for (const capability of missing) {
Object.defineProperty(store, capability, { value: undefined });
}
store.updateHeader = async () => assert.fail('Must not fall back to header writes');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3. requireSessionConfigurationStore is a single if (!a || !b), so the three missing-capability cases exercise the same throw; the third is the union of the first two. One case is enough. The two assert.fail probes are also unreachable now: transitionSessionConfiguration throws at its first line before any store write could happen. The valuable part of this block is that fallback writes are forbidden, and one case says that.

Comment thread packages/runtime/src/session-manager.ts Outdated
readHeaderRecordSnapshot?(sessionId: string): Promise<VersionedSessionHeader>;
/**
* Atomically check the expected revision and commit configuration, execution
* boundary and the new revision. Requires readHeaderRecordSnapshot.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3. This second paragraph restates the first (capabilities may be absent, no unversioned fallback). The first paragraph is the one that answers #4795; this one can go.

Comment thread packages/runtime/src/session-manager.ts Outdated
return headerToSummary(next);
}

async setExecutionBoundaryKind(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the top-level comment: this method persists permissionMode outside transitionSessionConfiguration with none of its checks and has no production caller. Same shape as the path this PR removes.

Remove the unused SessionManager boundary setter and its unversioned
Store requirement. Migrate descendant revocation and admission checks
to configuration authority and remove obsolete direct-route fixtures.

Split stale configuration revisions from gated concurrent boundary
conflicts, asserting each error precisely. Simplify capability tests
and explain Runtime optionality alongside the atomic Store contract.

Refs apache#4795

Generated-by: Codex
@chinawch007

Copy link
Copy Markdown
Contributor Author

Overall review

#5300 (review)

Thanks for checking this. I agree that retaining SessionManager.setExecutionBoundaryKind leaves the same architectural problem in place. The follow-up (b844456f0) removes that method as well as the unversioned write requirement from Runtime's SessionStore. The shared transition policy and Storage transaction implementation remain intact; Desktop and CLI still use the configuration operation.

I migrated the descendant shell-revocation and missing-admission-authority tests to transitionSessionConfiguration, kept the configuration-path read/write/network revocation cases, and removed the obsolete direct-route cases and their unused fixture. The direct-only same-mode Explore reset is no longer an API to preserve; the production configuration path's no-op semantics are unchanged.

The revised description now explains why the Store capabilities remain optional: production SessionAuthorityStore already requires both, while lightweight Runtime test stores do not all need configuration mutation. I also left the “fail without it” box unchecked, as discussed.

After rebuilding, all 559 affected tests passed. Full lint, format, build, typecheck, and Desktop/UI knip checks also passed. The full workspace test suite and manual Desktop interaction were not run. Codex assisted with the implementation, verification, and drafting these replies; the follow-up commit retains its Generated-by: Codex trailer.

Concurrent update errors

#5300 (comment)

Agreed that accepting either error obscured the test contract. I split this into two deterministic cases:

  • Commit a widening, then reuse the original snapshot and strictly assert SessionConfigurationRevisionConflictError, including expected/actual revisions and unchanged committed state.
  • Use a shared configuration snapshot and gate both initial boundary reads until both requests have observed the same Explore boundary. Then strictly assert that the losing request gets the boundary operation_conflict, without stopping the active Turn or revoking its shells. A fresh retry remains blocked as narrowing until the Turn settles.

One detail I found when implementing the suggestion: pre-reading the configuration snapshot alone does not guarantee a configuration-revision error. Inside the serialized commit, the boundary revision is checked before the second configuration-revision check. The explicit read gate fixes that interleaving, while the separate stale-snapshot case pins the revision-conflict result. No production error ordering or Host mapping changed.

Missing Store capabilities

#5300 (comment)

Removed the redundant “both missing” case and both assert.fail write probes. I retained the two individual missing-capability cases because they specify that each method is required: checking only one method in a future change should not silently weaken the contract. Each case now only asserts operation_unavailable and unchanged header/revision/boundary state.

Store contract comments

#5300 (comment)

Combined the comments into one contract block. It explains the paired capabilities, why Runtime keeps them optional despite the production authority requiring them, and the absence of a fallback. I retained the atomic expected-revision/configuration/boundary requirement because that is a separate implementation obligation, rather than a restatement of optionality.

Remaining boundary setter

#5300 (comment)

Removed SessionManager.setExecutionBoundaryKind and its requirement on Runtime's Store contract. The remaining useful tests now exercise transitionSessionConfiguration; duplicate direct-route cases and the unused AtomicBoundaryMemorySessionStore are gone. Storage's transaction primitive and the shared permission-transition policy remain in place.

@github-actions github-actions Bot added effort/L Under 1000 readable lines and removed effort/M Under 500 readable lines labels Sep 16, 2026

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at head b844456f against main (ea990ca). Comment only.

Premise and blast radius — verified

The removed path was unreachable in production.

  • SessionManager.setPermissionMode has no production caller. Desktop reaches permission changes through sessions:setPermissionModeupdateConfiguration (apps/desktop/src/main/runtime-host-session-catalog-ipc-main.ts:167-169), and the CLI driver's setPermissionMode is a different method on SessionDriver that already routes through session.configuration.update (packages/cli/src/runtime-host-session-driver.ts:683-698).
  • The only non-test setExecutionBoundaryKind call site, packages/cli/src/run-command-core.ts:343, is on MakaRunRuntime (run-command-core.ts:66), which runtime-host-run-command.ts:371-373 forwards to the driver's configuration update. Neither name survives anywhere in packages/**/src outside tests.
  • The branch condition was keyed on store capabilities, not on persisted session state, and the only production store (SessionAuthorityStore, wired at packages/storage/src/execution-stores.ts) implements both readHeaderRecordSnapshot and updateSessionConfiguration. No stored flag, column or legacy row could have selected the old branch.

No on-disk state depended on it. permissionMode and the boundary rows are read identically by the surviving authority, and nothing in the read/migration path changed. The legacy-state coverage that matters still exists and is untouched: the v1 WorkHub coordination repair persists a legacy-shaped header (toolProfile: workhub-coordination-v1 + explore) straight through Storage and repairs it via transitionConfiguration (packages/runtime-host/src/server/workhub-coordination-coordinator.ts:1050-1070), and the read model still projects legacy persisted mode_change system notes (session-manager.test.ts:9659-9672). packages/runtime is private: true, so there is no published-surface concern either.

Removal is complete within Runtime. No setPermissionMode, setPermissionModeWithLegacyStore, sessionConfigurationWithPermissionMode or executionBoundaryMatchesPermissionMode references remain in session-manager.ts; no route === 'direct' fixtures or "compatibility bridge" copy remain in the tests; and no new unused imports were introduced (the import-only symbols in the test file, e.g. createWorkspaceWritePermissionProfile, were already import-only on main).

Coverage migration is net-neutral to better. Name-diffing the test file base → head: the three deleted names (temporarily preserves setPermissionMode for legacy SessionStore implementations, the legacy concurrency route, the direct boundary route on Explore revocation) are replaced by configuration changes require Store capability: *, configuration authority rejects a stale revision…, serializes configuration commits that observed the same execution boundary and configuration narrowing revokes descendant background shell authority. Widening during an active Turn is still pinned (assert.strictEqual(backend?.stopCalls, 0) at session-manager.test.ts:4821), and pending-Interaction rejection, Deep Research label cleanup and descendant revocation all moved onto transitionSessionConfiguration. The missing-capability tests are now one case per capability and assert operation_unavailable plus an unchanged snapshot and boundary, which is the property that actually matters (no silent fallback write).

The doc comment at session-manager.ts:644-651 is accurate about why the pair stays optional, and splitting the stale-revision test from the boundary-fence test is the right shape.

Nits

  1. configurationForHeader drops executorId (session-manager.test.ts:14901). The helper it replaces (sessionConfigurationWithPermissionMode) passed executorId: header.executorId, and sessionConfigurationMatchesExceptPermissionMode compares executorId. For a plugin-executor-backed session the migrated tests would therefore silently compute permissionModeOnly === false and exercise commitExecutionResourceTransition rather than the commitExecutionBoundaryTransition path the test names describe. Every current fixture is ai-sdk, so nothing is wrong today — but this migration is the cheap moment to keep the helper field-complete.

  2. The dropped AtomicBoundaryMemorySessionStore invariant has no replacement. Its updateHeader threw permissionMode must be projected by the boundary transition whenever anything outside a boundary commit patched permissionMode. Nothing in the head tests asserts that property any more, even though VersionedConfigurationMemorySessionStore.updateSessionConfiguration (session-manager.test.ts:14181) performs exactly the two-step write it was guarding against. A few lines of guard there would keep #4795's "one authority" property pinned at the store double.

  3. Storage still exposes the same shape this PR deletes — follow-up, not a blocker. setExecutionBoundaryKind remains on the authority store handed to Runtime (packages/storage/src/execution-stores.ts:438), on SessionStore (packages/storage/src/session-store.ts:919), and as setExecutionBoundaryKindSync (packages/storage/src/sqlite-session-metadata-store.ts:4796), which still projects permissionMode/labels into the header (sqlite-session-metadata-store.ts:4889-4893). After this PR nothing in production calls it. I think leaving it is the right scope here (separate contract, own tests), but a one-line note that Storage is deliberately out of scope would close out #4795's acceptance criterion rather than leaving it half-met.

AI use: generative tooling helped me survey callers and diff base/head test names; every claim above was checked against the code at b844456f.

Preserve executorId when migrating permission changes to configuration
authority and cover widening with an active plugin-executor Turn.

Reject permissionMode patches through ordinary and versioned header
updates in the configuration Store double. Keep header and boundary
projection inside configuration commits and remove the unused memory
boundary setter and duplicate header write.

Verify the executor regression fails without the fix and injected header
permission writes are rejected. All 560 affected tests and the prescribed
lint, format, build, typecheck, and knip checks pass.

Refs apache#4795

Generated-by: Codex
@chinawch007

Copy link
Copy Markdown
Contributor Author

Thanks for checking the production callers and the migrated coverage. I've addressed the two test issues in 239da0784 and clarified the Storage scope below:

  1. Preserve executorId. configurationForHeader now copies it from the header. A new regression test uses a plugin-executor session with an active Turn reported by the Runtime kernel and verifies that permission widening succeeds, retains the executor identity, and neither stops nor disposes the backend. Removing the field copy makes the test fail with session_busy, confirming that it detects the unintended resource-transition path.

  2. Restore the single-authority guard. VersionedConfigurationMemorySessionStore.updateHeader now rejects patches containing permissionMode, and updateHeaderVersioned goes through the same guard. Only updateSessionConfiguration projects the permission mode, boundary, and revision. I also removed the memory store's unused boundary setter and its duplicate header write. Mutation checks confirmed that injecting either ordinary or versioned header permission writes makes the test fail at the guard; disabling the guard lets the ordinary bypass pass again. This checks which entry point performs the write; the existing SQLite transaction/rollback tests continue to cover persistence atomicity.

  3. Keep Storage's public boundary mutation API outside this PR. One distinction worth preserving: the private setExecutionBoundaryKindSync is still called by production updateSessionConfiguration inside its transaction. It remains part of the canonical configuration commit implementation. Any follow-up should distinguish that implementation from the publicly exposed boundary setter.

Validation: all 560 tests across the same nine affected test files passed after rebuilding, along with lint, format, full build, typecheck, and both prescribed knip checks. The full workspace test suite and manual Desktop interaction were not run.

AI use: Codex assisted with the implementation, verification, and this reply draft; the follow-up commit includes Generated-by: Codex.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/L Under 1000 readable lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

refactor(storage): Remove the obsolete SessionManager.setPermissionMode compatibility path

2 participants